Before v0.104.0, code executed at renderer level (e.g. front-end scripts)
could directly require @electron/remote or even Node.js APIs
(e.g. process or fs). While this makes them powerful,
it also exposes a significant attack surface — any XSS or untrusted script
in a note becomes remote code execution on the host.
To mitigate this, the client-side no longer has access to these APIs and everything must go through the Electron API instead.
The Frontend and Backend APIs reside in a global variable, api,
that can be used anywhere in the script. For example, to display a message
to the user the following front-end script can be used:
api.showMessage("Hello world.");
The Electron API is different: it lives on window.electronApi (exposed
by the desktop preload script), not on api, and is only available
in the Electron desktop build. Frontend scripts that use it should guard
with if (window.electronApi) so they degrade cleanly in the
browser and standalone (WASM) builds.
window.electronApi?.window.setZoomFactor(1.2);
Some of the APIs have additional safe-guards built in which prevent unauthorized access.
The shell group is the most heavily validated: openExternal is
restricted to an allowlisted set of URL schemes (blocking file:, smb:,
Follina-class schemes, etc.), openPath must resolve under the
Trilium data directory or temporary directory, and downloadURL is
locked to the app's own origin. Invalid input throws on the main side rather
than being silently passed through.